package com.sheepone.util.beanconvert.excel;

import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Cell;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.sheepone.util.ClassUtil;
import com.sheepone.util.MapUtil;
import com.sheepone.util.ObjectUtil;
import com.sheepone.util.ReflectUtil;
import com.sheepone.util.beanconvert.anno.ValueFilters;
import com.sheepone.util.string.StringUtil;
import com.sheepone.util.algorithm.SortUtil;
import com.sheepone.util.anno.*;
import com.sheepone.util.enums.SortType;
import com.sheepone.util.beanconvert.filter.ValueFilter;
import org.apache.commons.lang.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

/**
 * @author Miss.杨
 * @description 基于
 * @see Alias
 * @see Regex
 * @see StartWith
 * @see ZeroIfNaN
 * @see ValueFilters
 * @since 2024/3/2 - 22:01
 */
public class SingleTypeExcelParser<T> extends AnalysisEventListener<T> {

    private final ValueFilter[] filters;

    private boolean headInvoked = false;
    private int lineNum;
    private final List<T> data = new ArrayList<>();

    private final boolean enableHumpMapping;

    private final HashMap<Field, List<Integer>> fieldIndexesMappings = new HashMap<>();

    private static final Logger logger = LoggerFactory.getLogger(new Object() {
    }.getClass().getEnclosingClass());

    public SingleTypeExcelParser(Class<T> clazz, int lineNum, boolean enableHumpMapping) {
        if (!ObjectUtil.isSingleType(clazz)) {
            throw new IllegalArgumentException("clazz " + clazz.getName() + " is not a single type");
        }
        if (lineNum != -1 && lineNum < 1) {
            throw new IllegalArgumentException("lineNum must be greater than 0");
        }
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            checkAnnotations(field);
            fieldIndexesMappings.put(field, new ArrayList<>());
        }
        this.lineNum = lineNum;
        this.enableHumpMapping = enableHumpMapping;
        ValueFilters valueFiltersAnno = clazz.getDeclaredAnnotation(ValueFilters.class);
        if (valueFiltersAnno == null) {
            this.filters = null;
        } else {
            Class<? extends ValueFilter>[] filters = valueFiltersAnno.value();
            if (filters.length == 0) {
                this.filters = null;
            } else {
                this.filters = Arrays.stream(filters).map(ClassUtil::getInstance).toArray(ValueFilter[]::new);
            }
        }
    }

    private static void checkAnnotations(Field field) {
        Annotation[] annotations = field.getDeclaredAnnotations();
        for (Annotation annotation : annotations) {
            Class<? extends Annotation> clazz = annotation.annotationType();
            if ((clazz == Alias.class
                    || clazz == Regex.class
                    || clazz == StartWith.class
                    || clazz == ZeroIfNaN.class)
                    && field.getDeclaredAnnotation(ExcelIgnore.class) == null) {
                throw new IllegalArgumentException("field " + field.getName() + " is annotated with " + clazz.getName() + " but is not annotated with @ExcelIgnore");
            }
        }
    }

    public List<T> getData() {
        return data;
    }

    private static void processHumpName(Map<String, Integer> map, Field field, List<Integer> indexes) {
        String name = field.getName();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String underscoreToCamelCase = StringUtil.underscoreToCamelCase(entry.getKey());
            Integer index = entry.getValue();
            if (name.equals(underscoreToCamelCase)) {
                indexes.add(index);
                return;
            }
        }
    }

    private static void processOriginalName(Map<String, Integer> map, Field field, List<Integer> indexes) {
        String originalName = field.getName();
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            String name = entry.getKey();
            Integer index = entry.getValue();
            if (originalName.equals(name)) {
                indexes.add(index);
                return;
            }
        }
    }

    private static void processAlias(Map<String, Integer> map, Field field, List<Integer> indexes) {
        Alias alias = field.getAnnotation(Alias.class);
        if (alias == null) {
            return;
        }
        String[] aliases = alias.value();
        for (String aliasName : aliases) {
            map.forEach((name, index) -> {
                if (aliasName.equals(name)) {
                    indexes.add(index);
                }
            });
        }
    }

    private static void processRegex(Map<String, Integer> map, Field field, List<Integer> indexes) {
        Regex regex = field.getAnnotation(Regex.class);
        if (regex == null) {
            return;
        }
        String value = regex.value();
        map.forEach((name, index) -> {
            if (name.matches(value)) {
                indexes.add(index);
            }
        });
    }

    private static void processStartWith(Map<String, Integer> map, Field field, List<Integer> indexes) {
        StartWith startWith = field.getDeclaredAnnotation(StartWith.class);
        if (startWith == null) {
            return;
        }
        String[] values = startWith.value();
        map.forEach((name, index) -> {
            for (String value : values) {
                if (name.startsWith(value)) {
                    indexes.add(index);
                }
            }
        });
    }

    /**
     * 处理 @ZeroIfNaN 注解
     *
     * @param field
     * @param string
     * @return
     */
    private static String processZeroIfNaN(Field field, String string) {
        ZeroIfNaN zeroIfNaN = field.getAnnotation(ZeroIfNaN.class);
        if (zeroIfNaN != null && String.valueOf(Double.NaN).equals(string)) {
            string = "0.00";
        }
        return string;
    }

    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        if (!headInvoked) {
            this.headInvoked = true;
            HashMap<String, Integer> map = new HashMap<>();
            MapUtil.removeNullValueEntry(headMap);
            headMap.forEach((k, v) -> {
                if (!v.isEmpty()) {
                    map.put(v, k);
                }
            });
            handleHeadRow(map, headMap);
        }
    }

    private void handleHeadRow(Map<String, Integer> nameMap, Map<Integer, String> indexMap) {
        fieldIndexesMappings.forEach(((field, indexes) -> {

            // 处理 @StartWith
            processStartWith(nameMap, field, indexes);

            // 处理 @Regex
            processRegex(nameMap, field, indexes);

            // 先处理可能是复合类型的相关注解，再处理只是单一类型的注解
            // 处理 @Alias
            processAlias(nameMap, field, indexes);

            // 直接根据名称匹配
            if (indexes.isEmpty()) {
                processOriginalName(nameMap, field, indexes);
            }

            // 处理驼峰映射
            if (indexes.isEmpty() && this.enableHumpMapping) {
                processHumpName(nameMap, field, indexes);
            }
            processSort(nameMap, indexMap, field, indexes);

        }));

    }

    private void processSort(Map<String, Integer> nameMap, Map<Integer, String> indexMap, Field field, List<Integer> indexes) {
        if (indexes.isEmpty()) {
            return;
        }
        Sort sortAnno = field.getDeclaredAnnotation(Sort.class);
        if (sortAnno == null) {
            // 默认按照Excel索引顺序排列
            Collections.sort(indexes);
            return;
        }
        ArrayList<String> strings = new ArrayList<>();
        for (Integer index : indexes) {
            String name = indexMap.get(index);
            strings.add(name);
        }
        SortType type = sortAnno.type();
        if (type == SortType.TAIL_NUM_DESC || type == SortType.TAIL_NUM_ASC) {
            SortUtil.tailNumSort(sortAnno.prefix(), strings, type);
        } else {
            SortUtil.customized(type == SortType.DEFAULT_ASC ? null : Collections.reverseOrder(), strings);
        }

        for (int i = 0; i < indexes.size(); i++) {
            indexes.set(i, nameMap.get(strings.get(i)));
        }

    }


    private void handleValueStrings(Field field, T instance, List<String> valueStrs) throws Exception {
        if (valueStrs.isEmpty()) {
            return;
        }
        // 过滤可选值
        if (this.filters != null) {
            for (ValueFilter filter : filters) {
                valueStrs = filter.handleValues(field, valueStrs);
            }
        }
        Class<?> fieldType = field.getType();
        // 处理单一类型
        if (ObjectUtil.isSingleType(fieldType)) {
            String string = valueStrs.get(0);
            if (ObjectUtil.isBasicType(fieldType)) {

                string = processZeroIfNaN(field, string);

                ReflectUtil.writeStringToBasicField(field, instance, string, fieldType);
            } else {
                throw new Exception("field type is not support " + fieldType.getName());
            }
        } else if (ObjectUtil.isBasicComponentType(fieldType)) {
            Class<?> type = fieldType.getComponentType();
            // 处理数组类型
            if (fieldType.isArray()) {
                ReflectUtil.writeStringsToBasicArray(field, instance, valueStrs, type);
            } else if (ClassUtils.isAssignable(fieldType, List.class)) {
                // todo
            } else if (ClassUtils.isAssignable(fieldType, Map.class)) {
                // todo
            } else if (ClassUtils.isAssignable(fieldType, Set.class)) {
                // todo
            } else {
                throw new Exception("field type is not support " + fieldType.getName());
            }
        } else {
            throw new Exception("field type is not support " + fieldType.getName());
        }
    }

    @Override
    public void invoke(T instance, AnalysisContext analysisContext) {
        ReadRowHolder rowHolder = analysisContext.readRowHolder();
        Map<Integer, Cell> cellMap = rowHolder.getCellMap();
        if (rowHolder.getRowIndex() == 0 && !headInvoked) {
            this.headInvoked = true;
            HashMap<String, Integer> nameMap = new HashMap<>();
            HashMap<Integer, String> indexMap = new HashMap<>();
            cellMap.forEach((k, v) -> {
                String cellValueString = getCellValueString((CellData<?>) v);
                if (cellValueString != null && !cellValueString.isEmpty()) {
                    nameMap.put(cellValueString, k);
                    indexMap.put(k, cellValueString);
                }
            });
            handleHeadRow(nameMap, indexMap);
        } else {
            if (lineNum == 0) {
                return;
            }
            if (lineNum != -1) {
                lineNum--;
            }
            fieldIndexesMappings.forEach(((field, indexes) -> {
                if (indexes.isEmpty()) {
                    return;
                }
                List<String> valueStrs = new ArrayList<>();
                for (Integer index : indexes) {
                    valueStrs.add(getCellValueString((CellData<?>) cellMap.get(index)));
                }

                try {
                    handleValueStrings(field, instance, valueStrs);
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }));
            data.add(instance);
        }
    }

    public static String getCellValueString(CellData<?> cellData) {
        CellDataTypeEnum type = cellData.getType();
        return switch (type) {
            case STRING, DATE, ERROR -> cellData.getStringValue();
            case NUMBER -> cellData.getNumberValue().toString();
            case BOOLEAN -> cellData.getBooleanValue().toString();
            default -> null;
        };
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        if (logger.isInfoEnabled()) {
            logger.info("解析完成，解析到数据{}条", data.size());
        }
    }
}